﻿#include "shader.h"
#include "texture.h"
#include <glad/glad.h>
#include <GLFW/glfw3.h>

using namespace std;
using namespace anip;

ShaderPart::ShaderPart(Type type, const string& code):
	_type(type)
{
	const char* c = code.c_str();
	_hshader = glCreateShader((GLenum)type) ERROR_CHECKED;
	glShaderSource(_hshader, 1, &c, nullptr) ERROR_CHECKED;
	glCompileShader(_hshader) ERROR_CHECKED;

	GLsizei len;
	char msg[4096];
	glGetShaderInfoLog(_hshader, 4096, &len, msg) ERROR_CHECKED;
	_message = msg;

	GLint status;
	glGetShaderiv(_hshader, GL_COMPILE_STATUS, &status) ERROR_CHECKED;
	if (!status)
	{
		string result;
		result.append("Shader compilation error:\n");
		result.append(msg);
		throw runtime_error(result.c_str());
	}
}

void anip::ShaderPart::finalize()
{
	glDeleteShader(_hshader) ERROR_CHECKED;
}

Shader::Shader()
{
	_hprog = glCreateProgram() ERROR_CHECKED;
}

Shader::Shader(const initializer_list<const ShaderPart*>& parts)
{
	assert(parts.size() > 0);
	_hprog = glCreateProgram() ERROR_CHECKED;
	for (const ShaderPart* part : parts)
	{
		attach(*part);
	}
	compile();
}

Shader* Shader::load(const string& vert, const string& frag)
{
	ShaderPart* v = new ShaderPart(ShaderPart::Type::Vertex, vert);
	ShaderPart* f = new ShaderPart(ShaderPart::Type::Fragment, frag);
	Shader* s = new Shader{ v, f };
	return s;
}

void Shader::attach(const ShaderPart& part)
{
	glAttachShader(_hprog, part.id()) ERROR_CHECKED;
	_parts.insert(&part);
}

void Shader::detach(const ShaderPart& part)
{
	glDetachShader(_hprog, part.id()) ERROR_CHECKED;
	_parts.erase(&part);
}

void Shader::compile()
{
	glLinkProgram(_hprog) ERROR_CHECKED;

	GLint status;
	glGetProgramiv(_hprog, GL_LINK_STATUS, &status) ERROR_CHECKED;
	_linked = status == GL_TRUE;

	if (!_linked)
	{
		GLsizei len;
		char msg[4096];
		glGetProgramInfoLog(_hprog, 4096, &len, msg) ERROR_CHECKED;

		string result;
		result.append("Program linking error:\n");
		result.append(msg);
		throw runtime_error(result.c_str());
	}
}

void Shader::use() const
{
	assert(_linked);
	glUseProgram(_hprog) ERROR_CHECKED;
}

void Shader::finalize()
{
	glDeleteProgram(_hprog) ERROR_CHECKED;
}

GLuint Shader::_uniformLocation(const string& name)
{
	assert(_linked);
	auto iter = _uniforms.find(name);
	GLint location;
	if (iter != _uniforms.end())
		location = iter->second;
	else
	{
		location = glGetUniformLocation(_hprog, name.c_str()) ERROR_CHECKED;
		//if (location == -1)
		//	throw invalid_argument("Uniform not found or invalid");
		_uniforms.insert(make_pair(name, location));
	}
	return location;
}

GLuint Shader::_attribLocation(const string& name)
{
	assert(_linked);
	auto iter = _attribs.find(name);
	GLint location;
	if (iter != _attribs.end())
		location = iter->second;
	else
	{
		location = glGetAttribLocation(_hprog, name.c_str()) ERROR_CHECKED;
		if (location == -1)
			throw runtime_error("Attribute not found or invalid");
		_attribs.insert(make_pair(name, location));
	}
	return location;
}

void Shader::set(const string& name, float value)
{
	glUniform1f(_uniformLocation(name), value) ERROR_CHECKED;
}

void Shader::set(const string& name, double value)
{
	glUniform1d(_uniformLocation(name), value) ERROR_CHECKED;
}

void Shader::set(const string& name, int value)
{
	glUniform1i(_uniformLocation(name), value) ERROR_CHECKED;
}

void Shader::set(const string& name, const Texture2D &tex, int i)
{
	tex.use(i);
	glUniform1i(_uniformLocation(name), i) ERROR_CHECKED;
}

void Shader::set(const string& name, int value1, int value2)
{
	glUniform2i(_uniformLocation(name), value1, value2) ERROR_CHECKED;
}

void Shader::set(const string& name, double value1, double value2)
{
	glUniform2d(_uniformLocation(name), value1, value2) ERROR_CHECKED;
	
}

void Shader::set(const string& name, float value1, float value2)
{
	glUniform2f(_uniformLocation(name), value1, value2) ERROR_CHECKED;
}

void anip::Shader::set(const string& name, invec2 v)
{
	glUniform2f(_uniformLocation(name), v.x, v.y) ERROR_CHECKED;
}

void Shader::set(const string& name, int value1, int value2, int value3)
{
	glUniform3i(_uniformLocation(name), value1, value2, value3) ERROR_CHECKED;
}

void Shader::set(const string& name, double value1, double value2, double value3)
{
	glUniform3d(_uniformLocation(name), value1, value2, value3) ERROR_CHECKED;
}

void Shader::set(const string& name, float value1, float value2, float value3)
{
	glUniform3f(_uniformLocation(name), value1, value2, value3) ERROR_CHECKED;
}

void Shader::set(const string& name, int value1, int value2, int value3, int value4)
{
	glUniform4i(_uniformLocation(name), value1, value2, value3, value4) ERROR_CHECKED;
}

void Shader::set(const string& name, double value1, double value2, double value3, double value4)
{
	glUniform4d(_uniformLocation(name), value1, value2, value3, value4) ERROR_CHECKED;
}

void Shader::set(const string& name, float value1, float value2, float value3, float value4)
{
	glUniform4f(_uniformLocation(name), value1, value2, value3, value4) ERROR_CHECKED;
}

void anip::Shader::set(const string& name, const color& c)
{
	glUniform4f(_uniformLocation(name), c.r, c.g, c.b, c.a) ERROR_CHECKED;
}

void Shader::set(const string& name, inmat3 v)
{
	glUniformMatrix3fv(_uniformLocation(name), 1, false, &v[0][0]) ERROR_CHECKED;
}